package listener

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"gorm.io/gorm"
	"miao-server/dao"
	"miao-server/global"
	"miao-server/lib/id"
	"miao-server/lib/utils"
	"miao-server/structs/base"
	"miao-server/structs/dto"
	"miao-server/structs/enum"
	"miao-server/structs/tables"
	"strings"
	"time"
)

func (c *TcpClient) GetDeviceId(in base.ClientResponse) (err error) {
	var (
		device tables.Device
		inst   base.ClientInstruct
	)
	c.UniqueCode = in.Result
	if c.UniqueCode != "" {
		c.tcpServer.clientMap[c.UniqueCode] = c
	}
	device, err = dao.DeviceDao.FindByUniqueCode(in.Result)
	if err != nil {
		return
	}
	if device.Id == 0 {
		inst = base.ClientInstruct{
			Code: enum.HandsUp,
			Body: "",
		}
	} else {
		inst = base.ClientInstruct{
			Code: enum.SetDeviceId,
			Body: fmt.Sprintf("%d", device.Id),
		}
		c.ClientId = device.Id
	}
	err = c.WriteMessage(inst)
	return
}

func (c *TcpClient) HandUp(msg dto.HandsUpMsg) (did int64, err error) {
	c.UniqueCode = msg.UniqueForNow
	if c.UniqueCode != "" {
		c.tcpServer.clientMap[c.UniqueCode] = c
	}
	global.HandUpCache[msg.UniqueForNow] = msg
	// 举手信息需要考虑是新设备还是异常设备
	// 1. 如果当前计算值和另外三处一致，就是正常情况。直接检查举手信息，然后更新 device 表
	// 2. 如果当前计算值和另外三处不一致，说明数据被为人改动或者硬件发生的变动。具体分为：
	// 2.1 另外三处一致，但是当前计算值不一样。说明硬件变更，检查其他采集信息的相似度，高度相识就通知客户端更新唯一值
	// 		不是高度相识就记录异常设备信息。
	if msg.UniqueForNow == msg.UniqueForRegistry &&
		msg.UniqueForNow == msg.UniqueForDB &&
		msg.UniqueForNow == msg.UniqueForFile {
		// 当前计算值和另外三处一致，说明是正常情况
		// 如果唯一值存在就直接更新设备信息。不存在，说明是新设备，需要考虑相似度
		var (
			device tables.Device
		)
		device, err = dao.DeviceDao.FindByUniqueCode(msg.UniqueForNow)
		if err != nil {
			return
		}
		if device.Id != 0 {
			err = c.updateDeviceInfo(msg, device)
			if err != nil {
				return
			}
			did = device.Id
		} else {
			// 说明是新设备，需要考虑相识度
			device = tables.Device{
				Id:              id.GetSnowId(),
				UniqueValue:     msg.UniqueForNow,
				BiosSerial:      msg.BiosSerial,
				MainBoardSerial: msg.MainBoardSerial,
				HardDiskSerial:  msg.HardDiskSerial,
				ActiveMAC:       msg.ActiveMAC,
				IP:              msg.IP,
				Hostname:        msg.Hostname,
				CreatedAt:       time.Now().Unix(),
				MapUnique:       "",
			}
			ms := c.checkDeviceBaseInfo(device)
			if len(ms) == 0 {
				// 没有重复设备，直接写入
				err = global.DB.Save(&device).Error
				if err != nil {
					return
				}
				global.AddDeviceMap(device)
				did = device.Id
			} else {
				// 有存在重复的可能，写入异常表
				var rd tables.DeviceAbnormal
				rd, err = dao.DeviceDao.FindAbnormalByUniqueCode(msg.UniqueForNow)
				if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
					return
				}
				if rd.Id != 0 {
					rd.MapUnique = ms
				} else {
					rd = tables.DeviceAbnormal{
						Id:                id.GetSnowId(),
						UniqueForNow:      msg.UniqueForNow,
						UniqueForDB:       msg.UniqueForDB,
						UniqueForFile:     msg.UniqueForFile,
						UniqueForRegistry: msg.UniqueForRegistry,
						BiosSerial:        msg.BiosSerial,
						MainBoardSerial:   msg.MainBoardSerial,
						HardDiskSerial:    msg.HardDiskSerial,
						ActiveMAC:         msg.ActiveMAC,
						IP:                msg.IP,
						Hostname:          msg.Hostname,
						LastOnlineTime:    time.Now().Unix(),
						CreatedAt:         time.Now().Unix(),
						MapUnique:         ms,
					}
				}
				err = global.DB.Save(rd).Error
				if err != nil {
					return
				}
			}
		}
	} else {
		// 说明四处信息不一致，就记录异常设备
		var rd tables.DeviceAbnormal
		rd, err = dao.DeviceDao.FindAbnormalByUniqueCode(msg.UniqueForNow)
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return
		}
		if rd.Id == 0 {
			rd.Id = id.GetSnowId()
			rd.UniqueForNow = msg.UniqueForNow
			rd.UniqueForDB = msg.UniqueForDB
			rd.UniqueForFile = msg.UniqueForFile
			rd.UniqueForRegistry = msg.UniqueForRegistry
			rd.BiosSerial = msg.BiosSerial
			rd.MainBoardSerial = msg.MainBoardSerial
			rd.HardDiskSerial = msg.HardDiskSerial
			rd.ActiveMAC = msg.ActiveMAC
			rd.IP = msg.IP
			rd.Hostname = msg.Hostname
			rd.LastOnlineTime = time.Now().Unix()
		} else {
			rd.UniqueForNow = msg.UniqueForNow
			rd.UniqueForDB = msg.UniqueForDB
			rd.UniqueForFile = msg.UniqueForFile
			rd.UniqueForRegistry = msg.UniqueForRegistry
			rd.BiosSerial = msg.BiosSerial
			rd.MainBoardSerial = msg.MainBoardSerial
			rd.HardDiskSerial = msg.HardDiskSerial
			rd.ActiveMAC = msg.ActiveMAC
			rd.IP = msg.IP
			rd.Hostname = msg.Hostname
			rd.LastOnlineTime = time.Now().Unix()
		}
		err = global.DB.Save(&rd).Error
		if err != nil {
			return
		}
	}
	return
}

func (c *TcpClient) HeartBeat(in base.ClientResponse) {
	var (
		err error
	)
	if in.Result == "ping" {
		return
	}
	var v1 dto.CollectInfo
	err = json.Unmarshal([]byte(in.Result), &v1)
	if err != nil {
		return
	}
	v2 := tables.LogCollect{
		Id:          id.GetSnowId(),
		DeviceId:    c.ClientId,
		UniqueCode:  global.DeviceCache[c.ClientId].UniqueValue,
		Cmd:         v1.Cmd,
		Args:        v1.Args,
		ElapsedTime: v1.ElapsedTime,
		Result:      v1.Result,
		ResultType:  v1.ResultType,
		CollectTime: v1.CreateAt,
		CreateAt:    time.Now().Unix(),
	}
	err = global.DB.Save(&v2).Error
	if err != nil {
		logrus.Error(err)
		return
	}
	msg := base.ClientInstruct{
		Code: enum.AckMsg,
		Body: fmt.Sprintf("%d", v1.Id),
	}
	err = c.WriteMessage(msg)
	if err != nil {
		logrus.Error(err)
	}
}

func (c *TcpClient) handPcLoad(in base.ClientResponse) (err error) {
	var pcLoadInfo dto.PcLoadInfo
	err = json.Unmarshal([]byte(in.Result), &pcLoadInfo)
	if err != nil {
		return
	}
	if pcLoadInfo.IsSave {
		var ll = tables.LogLoad{
			Id:         id.GetSnowId(),
			DeviceId:   c.ClientId,
			UniqueCode: c.UniqueCode,
			PcLoadInfo: dto.PcLoadInfo{
				StartTime: pcLoadInfo.StartTime,
				EndTime:   pcLoadInfo.EndTime,
				Count:     pcLoadInfo.Count,
				Details:   pcLoadInfo.Details,
				IsSave:    pcLoadInfo.IsSave,
			},
			CreateAt: 0,
		}
		err = dao.DeviceDao.SaveLogLoad(ll)
		if err != nil {
			return
		}
	}
	c.cacheResponse(in, enum.LoadCount)
	return
}

func (c *TcpClient) sendClientPolicy() (err error) {
	var (
		bs []byte
	)
	bs, err = json.Marshal(global.Policies)
	if err != nil {
		return
	}
	msg := base.ClientInstruct{
		Code: enum.UpdateAllPolicy,
		Body: string(bs),
	}
	return c.WriteMessage(msg)
}

// updateDeviceInfo 更新设备基础信息
func (c *TcpClient) updateDeviceInfo(in dto.HandsUpMsg, device tables.Device) (err error) {
	var b bool
	if device.BiosSerial != in.BiosSerial {
		device.BiosSerial = in.BiosSerial
		b = true
	}
	if device.MainBoardSerial != in.MainBoardSerial {
		device.MainBoardSerial = in.MainBoardSerial
		b = true
	}
	if device.HardDiskSerial != in.HardDiskSerial {
		device.HardDiskSerial = in.HardDiskSerial
		b = true
	}
	if device.Hostname != in.Hostname {
		device.Hostname = in.Hostname
		b = true
	}
	device.IP = in.IP
	device.ActiveMAC = in.ActiveMAC
	if b {
		err = global.DB.Save(&device).Error
	}
	return
}

// checkDeviceBaseInfo ,包括主板、bios、硬盘序列号 和主机名称，4个有三个相同就返回true
func (c *TcpClient) checkDeviceBaseInfo(device tables.Device) (ms string) {
	mapStr := make([]string, 0)
	for _, item := range global.DeviceMap {
		var i int
		if device.BiosSerial == item.BiosSerial {
			i++
		}
		if device.MainBoardSerial == item.MainBoardSerial {
			i++
		}
		if device.HardDiskSerial == item.HardDiskSerial {
			i++
		}
		if device.Hostname == item.Hostname {
			i++
		}
		if i >= 3 {
			mapStr = append(mapStr, item.UniqueValue)
		}
	}
	old := strings.Split(device.MapUnique, ",")
	old = append(old, mapStr...)
	ms = utils.ArrStringToStr(old)
	return
}

// cacheResponse 缓存响应
func (c *TcpClient) cacheResponse(in base.ClientResponse, what int) {
	v1 := dto.InstructResponseDto{
		ClientResponse: in,
		ServiceTime:    time.Now().Unix(),
	}
	switch what {
	case enum.ExecutePlugin:
		global.InstructResponseCache[fmt.Sprintf("%s_%s_%s", c.UniqueCode, in.Cmd, in.Args)] = v1
	case enum.UploadPolicy:
		global.InstructResponseCache[fmt.Sprintf("%s_%s_%s", c.UniqueCode, fmt.Sprintf("%d", enum.UploadPolicy), "nil")] = v1
	case enum.LoadCount:
		global.InstructResponseCache[fmt.Sprintf("%s_%s_%s", c.UniqueCode, fmt.Sprintf("%d", enum.LoadCount), "nil")] = v1
	}
}
